package org.dromara.common.encrypt.interceptor

import cn.hutool.core.collection.CollUtil
import cn.hutool.core.convert.Convert
import cn.hutool.core.util.ObjectUtil
import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.ibatis.executor.parameter.ParameterHandler
import org.apache.ibatis.executor.resultset.ResultSetHandler
import org.apache.ibatis.plugin.*
import org.dromara.common.core.utils.StringUtils
import org.dromara.common.encrypt.annotation.EncryptField
import org.dromara.common.encrypt.core.EncryptContext
import org.dromara.common.encrypt.core.EncryptorManager
import org.dromara.common.encrypt.enumd.AlgorithmType
import org.dromara.common.encrypt.enumd.EncodeType
import org.dromara.common.encrypt.properties.EncryptorProperties
import java.lang.reflect.Field
import java.sql.Statement
import java.util.*

/**
 * 出参解密拦截器
 *
 * @author 老马
 * @version 4.6.0
 * @updater LikeYouDo
 * @date 2025/1/14 13:46
 */
@Intercepts(Signature(type = ResultSetHandler::class, method = "handleResultSets", args = [Statement::class]))
open class MybatisDecryptInterceptor(
    private val encryptorManager: EncryptorManager,
    private val defaultProperties: EncryptorProperties
) : Interceptor {

    companion object {
        private val log = KotlinLogging.logger { }
    }

    @Throws(Throwable::class)
    override fun intercept(invocation: Invocation): Any? {
        // 开始进行参数解密
        val resultSetHandler = invocation.target as ResultSetHandler
        val parameterHandlerField = resultSetHandler.javaClass.getDeclaredField("parameterHandler")
        parameterHandlerField.setAccessible(true)
        val target = parameterHandlerField.get(resultSetHandler)
        if (target is ParameterHandler) {
            val parameterObject = target.parameterObject
            if (ObjectUtil.isNotNull(parameterObject) && parameterObject !is String) {
                this.decryptHandler(parameterObject)
            }
        }
        // 获取执行mysql执行结果
        val result = invocation.proceed() ?: return null
        this.decryptHandler(result)
        return result
    }

    /**
     * 解密对象
     *
     * @param sourceObject 待加密对象
     */
    private fun decryptHandler(sourceObject: Any?) {
        if (sourceObject == null) {
            return
        }
        if (sourceObject is Map<*, *>) {
            HashSet(sourceObject.values).forEach(this::decryptHandler)
            return
        }
        if (sourceObject is List<*>) {
            if (CollUtil.isEmpty(sourceObject)) {
                return
            }
            // 判断第一个元素是否含有注解。如果没有直接返回，提高效率
            val firstItem = sourceObject[0]
            if (firstItem == null || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.javaClass))) {
                return
            }
            sourceObject.forEach(this::decryptHandler)
            return
        }
        // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
        val fields: Set<Field> = encryptorManager.getFieldCache(sourceObject.javaClass) ?: return
        try {
            for (field in fields) {
                field[sourceObject] = decryptField(Convert.toStr(field[sourceObject]), field)
            }
        } catch (e: Exception) {
            log.error(e) { "处理解密字段时出错" }
        }
    }

    /**
     * 字段值进行加密。通过字段的批注注册新的加密算法
     *
     * @param value 待加密的值
     * @param field 待加密字段
     * @return 加密后结果
     */
    private fun decryptField(value: String?, field: Field): String? {
        if (value == null) {
            return null
        }
        val encryptField = field.getAnnotation(EncryptField::class.java)
        val encryptContext = EncryptContext()
        encryptContext.algorithm =
            if (encryptField.algorithm == AlgorithmType.DEFAULT) defaultProperties.algorithm else encryptField.algorithm
        encryptContext.encode =
            if (encryptField.encode == EncodeType.DEFAULT) defaultProperties.encode else encryptField.encode
        encryptContext.password =
            if (StringUtils.isBlank(encryptField.password)) defaultProperties.password else encryptField.password
        encryptContext.privateKey =
            if (StringUtils.isBlank(encryptField.privateKey)) defaultProperties.privateKey else encryptField.privateKey
        encryptContext.publicKey =
            if (StringUtils.isBlank(encryptField.publicKey)) defaultProperties.publicKey else encryptField.publicKey
        return this.encryptorManager.decrypt(value, encryptContext)
    }

    override fun plugin(target: Any?): Any {
        return Plugin.wrap(target, this)
    }

    override fun setProperties(properties: Properties?) {
    }
}
